#include "gtkicontheme.h"
#include "gtkwidgetprivate.h"
#include "gtkstylecontextprivate.h"
+#include "gtktexthandleprivate.h"
#include "a11y/gtkentryaccessible.h"
gchar *placeholder_text;
+ GtkTextHandle *text_handle;
+
gfloat xalign;
gint ascent; /* font ascent in pango units */
guint select_words : 1;
guint select_lines : 1;
guint truncate_multiline : 1;
+ guint cursor_handle_dragged : 1;
+ guint selection_handle_dragged : 1;
};
struct _EntryIconInfo
static void gtk_entry_update_cached_style_values(GtkEntry *entry);
static gboolean get_middle_click_paste (GtkEntry *entry);
+/* GtkTextHandle handlers */
+static void gtk_entry_handle_dragged (GtkTextHandle *handle,
+ GtkTextHandlePosition pos,
+ gint x,
+ gint y,
+ GtkEntry *entry);
static void begin_change (GtkEntry *entry);
static void end_change (GtkEntry *entry);
gtk_style_context_add_class (context, GTK_STYLE_CLASS_ENTRY);
gtk_entry_update_cached_style_values (entry);
+
+ priv->text_handle = _gtk_text_handle_new (GTK_WIDGET (entry));
+ g_signal_connect (priv->text_handle, "handle-dragged",
+ G_CALLBACK (gtk_entry_handle_dragged), entry);
}
static void
if (priv->recompute_idle)
g_source_remove (priv->recompute_idle);
+ g_object_unref (priv->text_handle);
g_free (priv->placeholder_text);
g_free (priv->im_module);
EntryIconInfo *icon_info = NULL;
gint i;
+ _gtk_text_handle_set_mode (priv->text_handle,
+ GTK_TEXT_HANDLE_MODE_NONE);
+
for (i = 0; i < MAX_ICONS; i++)
{
if ((icon_info = priv->icons[i]) != NULL)
gtk_entry_adjust_scroll (entry);
gtk_entry_update_primary_selection (entry);
-
+ _gtk_text_handle_set_relative_to (priv->text_handle, priv->text_area);
/* If the icon positions are already setup, create their windows.
* Otherwise if they don't exist yet, then construct_icon_info()
gtk_entry_reset_layout (entry);
gtk_im_context_set_client_window (priv->im_context, NULL);
+ _gtk_text_handle_set_relative_to (priv->text_handle, NULL);
clipboard = gtk_widget_get_clipboard (widget, GDK_SELECTION_PRIMARY);
if (gtk_clipboard_get_owner (clipboard) == G_OBJECT (entry))
g_free (ranges);
return retval;
}
-
+
+static void
+gtk_entry_move_handle (GtkEntry *entry,
+ GtkTextHandlePosition pos,
+ gint x,
+ gint y,
+ gint height)
+{
+ GtkEntryPrivate *priv = entry->priv;
+
+ if (!_gtk_text_handle_get_is_dragged (priv->text_handle, pos) &&
+ (x < 0 || x > gdk_window_get_width (priv->text_area)))
+ {
+ /* Hide the handle if it's not being manipulated
+ * and fell outside of the visible text area.
+ */
+ _gtk_text_handle_set_visible (priv->text_handle, pos, FALSE);
+ }
+ else
+ {
+ GdkRectangle rect;
+
+ rect.x = CLAMP (x, 0, gdk_window_get_width (priv->text_area));
+ rect.y = y;
+ rect.width = 1;
+ rect.height = height;
+
+ _gtk_text_handle_set_visible (priv->text_handle, pos, TRUE);
+ _gtk_text_handle_set_position (priv->text_handle, pos, &rect);
+ }
+}
+
+static gint
+gtk_entry_get_selection_bound_location (GtkEntry *entry)
+{
+ GtkEntryPrivate *priv = entry->priv;
+ PangoLayout *layout;
+ PangoRectangle pos;
+ gint x;
+ const gchar *text;
+ gint index;
+
+ layout = gtk_entry_ensure_layout (entry, FALSE);
+ text = pango_layout_get_text (layout);
+ index = g_utf8_offset_to_pointer (text, priv->selection_bound) - text;
+ pango_layout_index_to_pos (layout, index, &pos);
+
+ if (gtk_widget_get_direction (GTK_WIDGET (entry)) == GTK_TEXT_DIR_RTL)
+ x = (pos.x + pos.width) / PANGO_SCALE;
+ else
+ x = pos.x / PANGO_SCALE;
+
+ return x;
+}
+
+static void
+gtk_entry_update_handles (GtkEntry *entry,
+ GtkTextHandleMode mode)
+{
+ GtkEntryPrivate *priv = entry->priv;
+ gint strong_x, height;
+ gint cursor, bound;
+
+ _gtk_text_handle_set_mode (priv->text_handle, mode);
+
+ /* Wait for recomputation before repositioning */
+ if (priv->recompute_idle != 0)
+ return;
+
+ height = gdk_window_get_height (priv->text_area);
+
+ gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, NULL);
+ cursor = strong_x - priv->scroll_offset;
+
+ if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+ {
+ gint start, end;
+
+ bound = gtk_entry_get_selection_bound_location (entry) - priv->scroll_offset;
+
+ if (priv->selection_bound > priv->current_pos)
+ {
+ start = cursor;
+ end = bound;
+ }
+ else
+ {
+ start = bound;
+ end = cursor;
+ }
+
+ /* Update start selection bound */
+ gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_START,
+ start, 0, height);
+ gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_SELECTION_END,
+ end, 0, height);
+ }
+ else
+ gtk_entry_move_handle (entry, GTK_TEXT_HANDLE_POSITION_CURSOR,
+ cursor, 0, height);
+}
+
static gint
gtk_entry_button_press (GtkWidget *widget,
GdkEventButton *event)
else if (event->button == GDK_BUTTON_PRIMARY)
{
gboolean have_selection = gtk_editable_get_selection_bounds (editable, &sel_start, &sel_end);
+ gboolean is_touchscreen;
+ GdkDevice *source;
+
+ source = gdk_event_get_source_device ((GdkEvent *) event);
+ is_touchscreen = gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN;
priv->select_words = FALSE;
priv->select_lines = FALSE;
priv->drag_start_y = event->y;
}
else
- gtk_editable_set_position (editable, tmp_pos);
+ {
+ gtk_editable_set_position (editable, tmp_pos);
+
+ if (is_touchscreen)
+ gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
+ }
break;
case GDK_2BUTTON_PRESS:
priv->in_drag = FALSE;
priv->select_words = TRUE;
gtk_entry_select_word (entry);
+
+ if (is_touchscreen)
+ gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
break;
case GDK_3BUTTON_PRESS:
priv->in_drag = FALSE;
priv->select_lines = TRUE;
gtk_entry_select_line (entry);
+ if (is_touchscreen)
+ gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_SELECTION);
break;
default:
if (priv->in_drag)
{
gint tmp_pos = gtk_entry_find_position (entry, priv->drag_start_x);
+ GdkDevice *source;
gtk_editable_set_position (GTK_EDITABLE (entry), tmp_pos);
+ source = gdk_event_get_source_device ((GdkEvent *) event);
+
+ if (gdk_device_get_source (source) == GDK_SOURCE_TOUCHSCREEN)
+ gtk_entry_update_handles (entry, GTK_TEXT_HANDLE_MODE_CURSOR);
+
priv->in_drag = 0;
}
}
else
{
+ GdkInputSource input_source;
+ GdkDevice *source;
+ guint length;
+
+ length = gtk_entry_buffer_get_length (get_buffer (entry));
+
if (event->y < 0)
tmp_pos = 0;
else if (event->y >= gdk_window_get_height (priv->text_area))
- tmp_pos = gtk_entry_buffer_get_length (get_buffer (entry));
+ tmp_pos = length;
else
tmp_pos = gtk_entry_find_position (entry, event->x + priv->scroll_offset);
+ source = gdk_event_get_source_device ((GdkEvent *) event);
+ input_source = gdk_device_get_source (source);
+
if (priv->select_words)
{
gint min, max;
if (priv->current_pos != max)
pos = min;
}
-
+
gtk_entry_set_positions (entry, pos, bound);
}
else
gtk_entry_set_positions (entry, tmp_pos, -1);
+
+ /* Update touch handles' position */
+ if (input_source == GDK_SOURCE_TOUCHSCREEN)
+ gtk_entry_update_handles (entry,
+ (priv->current_pos == priv->selection_bound) ?
+ GTK_TEXT_HANDLE_MODE_CURSOR :
+ GTK_TEXT_HANDLE_MODE_SELECTION);
}
-
+
return TRUE;
}
gtk_entry_reset_blink_time (entry);
gtk_entry_pend_cursor_blink (entry);
+ _gtk_text_handle_set_mode (priv->text_handle,
+ GTK_TEXT_HANDLE_MODE_NONE);
if (priv->editable)
{
GtkEntryCompletion *completion;
GdkKeymap *keymap;
+ _gtk_text_handle_set_mode (priv->text_handle,
+ GTK_TEXT_HANDLE_MODE_NONE);
+
gtk_widget_queue_draw (widget);
keymap = gdk_keymap_get_for_display (gtk_widget_get_display (widget));
if (gtk_widget_has_screen (GTK_WIDGET (entry)))
{
+ GtkTextHandleMode handle_mode;
+
gtk_entry_adjust_scroll (entry);
gtk_widget_queue_draw (GTK_WIDGET (entry));
update_im_cursor_location (entry);
+
+ handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
+
+ if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
+ gtk_entry_update_handles (entry, handle_mode);
}
return FALSE;
}
}
+static void
+gtk_entry_handle_dragged (GtkTextHandle *handle,
+ GtkTextHandlePosition pos,
+ gint x,
+ gint y,
+ GtkEntry *entry)
+{
+ gint cursor_pos, selection_bound_pos, tmp_pos;
+ GtkEntryPrivate *priv = entry->priv;
+ GtkTextHandleMode mode;
+ gint *min, *max;
+
+ cursor_pos = priv->current_pos;
+ selection_bound_pos = priv->selection_bound;
+ mode = _gtk_text_handle_get_mode (handle);
+ tmp_pos = gtk_entry_find_position (entry, x + priv->scroll_offset);
+
+ if (mode == GTK_TEXT_HANDLE_MODE_CURSOR ||
+ cursor_pos >= selection_bound_pos)
+ {
+ max = &cursor_pos;
+ min = &selection_bound_pos;
+ }
+ else
+ {
+ max = &selection_bound_pos;
+ min = &cursor_pos;
+ }
+
+ if (pos == GTK_TEXT_HANDLE_POSITION_SELECTION_END)
+ {
+ if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+ {
+ gint min_pos;
+
+ min_pos = MAX (*min + 1, 0);
+ tmp_pos = MAX (tmp_pos, min_pos);
+ }
+
+ *max = tmp_pos;
+ }
+ else
+ {
+ if (mode == GTK_TEXT_HANDLE_MODE_SELECTION)
+ {
+ gint max_pos;
+
+ max_pos = *max - 1;
+ *min = MIN (tmp_pos, max_pos);
+ }
+ }
+
+ if (cursor_pos != priv->current_pos ||
+ selection_bound_pos != priv->selection_bound)
+ {
+ if (mode == GTK_TEXT_HANDLE_MODE_CURSOR)
+ gtk_entry_set_positions (entry, cursor_pos, cursor_pos);
+ else
+ gtk_entry_set_positions (entry, cursor_pos, selection_bound_pos);
+
+ gtk_entry_update_handles (entry, mode);
+ }
+}
+
/**
* gtk_entry_reset_im_context:
* @entry: a #GtkEntry
}
}
+static gboolean
+gtk_entry_get_is_selection_handle_dragged (GtkEntry *entry)
+{
+ GtkEntryPrivate *priv = entry->priv;
+ GtkTextHandlePosition pos;
+
+ if (_gtk_text_handle_get_mode (priv->text_handle) != GTK_TEXT_HANDLE_MODE_SELECTION)
+ return FALSE;
+
+ if (priv->current_pos >= priv->selection_bound)
+ pos = GTK_TEXT_HANDLE_POSITION_SELECTION_START;
+ else
+ pos = GTK_TEXT_HANDLE_POSITION_SELECTION_END;
+
+ return _gtk_text_handle_get_is_dragged (priv->text_handle, pos);
+}
+
static void
gtk_entry_adjust_scroll (GtkEntry *entry)
{
PangoLayout *layout;
PangoLayoutLine *line;
PangoRectangle logical_rect;
+ GtkTextHandleMode handle_mode;
if (!gtk_widget_get_realized (GTK_WIDGET (entry)))
return;
priv->scroll_offset = CLAMP (priv->scroll_offset, min_offset, max_offset);
- /* And make sure cursors are on screen. Note that the cursor is
- * actually drawn one pixel into the INNER_BORDER space on
- * the right, when the scroll is at the utmost right. This
- * looks better to to me than confining the cursor inside the
- * border entirely, though it means that the cursor gets one
- * pixel closer to the edge of the widget on the right than
- * on the left. This might need changing if one changed
- * INNER_BORDER from 2 to 1, as one would do on a
- * small-screen-real-estate display.
- *
- * We always make sure that the strong cursor is on screen, and
- * put the weak cursor on screen if possible.
- */
+ if (gtk_entry_get_is_selection_handle_dragged (entry))
+ {
+ /* The text handle corresponding to the selection bound is
+ * being dragged, ensure it stays onscreen even if we scroll
+ * cursors away, this is so both handles can cause content
+ * to scroll.
+ */
+ strong_x = weak_x = gtk_entry_get_selection_bound_location (entry);
+ }
+ else
+ {
+ /* And make sure cursors are on screen. Note that the cursor is
+ * actually drawn one pixel into the INNER_BORDER space on
+ * the right, when the scroll is at the utmost right. This
+ * looks better to to me than confining the cursor inside the
+ * border entirely, though it means that the cursor gets one
+ * pixel closer to the edge of the widget on the right than
+ * on the left. This might need changing if one changed
+ * INNER_BORDER from 2 to 1, as one would do on a
+ * small-screen-real-estate display.
+ *
+ * We always make sure that the strong cursor is on screen, and
+ * put the weak cursor on screen if possible.
+ */
+ gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
+ }
- gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &strong_x, &weak_x);
-
strong_xoffset = strong_x - priv->scroll_offset;
if (strong_xoffset < 0)
}
g_object_notify (G_OBJECT (entry), "scroll-offset");
+
+ handle_mode = _gtk_text_handle_get_mode (priv->text_handle);
+
+ if (handle_mode != GTK_TEXT_HANDLE_MODE_NONE)
+ gtk_entry_update_handles (entry, handle_mode);
}
static void
gtk_widget_get_allocation (widget, &allocation);
- /* Cursor position, layout offset, border width, and widget allocation */
+ /* Cursor/char position, layout offset, border width, and widget allocation */
gtk_entry_get_cursor_locations (entry, CURSOR_STANDARD, &x, NULL);
get_layout_position (entry, &layout_x, NULL);
_gtk_entry_get_borders (entry, &borders);